// 版权所有2012 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package filepath

import (
	"strings"
	"syscall"
)

// normVolumeName类似于VolumeName，但使驱动器号大写。
// EvalSymlinks的结果必须是唯一的，因此我们有
// EvalSymlinks（`c:\a`）==EvalSymlinks（`c:\a`）。
func normVolumeName(path string) string {
	volume := VolumeName(path)

	if len(volume) > 2 { // isUNC 
		return volume
	}

	return strings.ToUpper(volume)
}

// normBase返回path的最后一个元素，大小写正确。
func normBase(path string) (string, error) {
	p, err := syscall.UTF16PtrFromString(path)
	if err != nil {
		return "", err
	}

	var data syscall.Win32finddata

	h, err := syscall.FindFirstFile(p, &data)
	if err != nil {
		return "", err
	}
	syscall.FindClose(h)

	return syscall.UTF16ToString(data.FileName[:]), nil
}

// baseIsDotDot报告path的最后一个元素是否为“.”。
// 应提前“清理”给定路径。
func baseIsDotDot(path string) bool {
	i := strings.LastIndexByte(path, Separator)
	return path[i+1:] == ".."
}

// toNorm返回保证唯一的规范化路径。
// 它应该接受以下格式：
// /*UNC路径（例如\\server\share\foo\bar）
// /*绝对路径（例如C:\foo\bar）
// /*相对路径以驱动器号开头（例如C:foo\bar，C:..\foo\bar，C:
// /*相对路径以“\”开头（例如\foo\bar）
// /*相对路径以不带“\”开头（例如foo\bar，…\foo\bar，…）。
// 返回的规范化路径将与输入路径采用相同的形式（上面列出的5种形式）。
// 如果两个路径A和B指示具有相同格式的相同文件，则toNorm（A）应等于toNorm（B）。
// normBase参数应等于normBase func，测试中除外。参见normBase func上的文档。
func toNorm(path string, normBase func(string) (string, error)) (string, error) {
	if path == "" {
		return path, nil
	}

	path = Clean(path)

	volume := normVolumeName(path)
	path = path[len(volume):]

	// 跳过特殊情况
	if path == "" || path == "." || path == `\` {
		return volume + path, nil
	}

	var normPath string

	for {
		if baseIsDotDot(path) {
			normPath = path + `\` + normPath

			break
		}

		name, err := normBase(volume + path)
		if err != nil {
			return "", err
		}

		normPath = name + `\` + normPath

		i := strings.LastIndexByte(path, Separator)
		if i == -1 {
			break
		}
		if i == 0 { // /`\Go`或'C:\Go`
			normPath = `\` + normPath

			break
		}

		path = path[:i]
	}

	normPath = normPath[:len(normPath)-1] // 删除尾部'\'

	return volume + normPath, nil
}

func evalSymlinks(path string) (string, error) {
	newpath, err := walkSymlinks(path)
	if err != nil {
		return "", err
	}
	newpath, err = toNorm(newpath, normBase)
	if err != nil {
		return "", err
	}
	return newpath, nil
}
